package com.xiaoshuidi.cloud.module.contract.service.contract.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoshuidi.cloud.framework.common.exception.ServiceException;
import com.xiaoshuidi.cloud.framework.common.pojo.CommonResult;
import com.xiaoshuidi.cloud.framework.common.pojo.PageParam;
import com.xiaoshuidi.cloud.framework.common.pojo.PageResult;
import com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils;
import com.xiaoshuidi.cloud.framework.mybatis.core.dataobject.BaseDO;
import com.xiaoshuidi.cloud.framework.security.core.util.SecurityFrameworkUtils;
import com.xiaoshuidi.cloud.module.contract.controller.admin.bill.vo.orderFees.OrderFeesVo;
import com.xiaoshuidi.cloud.module.contract.controller.admin.bill.vo.rentOrder.RentOrderCreateReq;
import com.xiaoshuidi.cloud.module.contract.controller.admin.contract.vo.ContractApplyPageReq;
import com.xiaoshuidi.cloud.module.contract.controller.admin.contract.vo.ContractApplyPageResp;
import com.xiaoshuidi.cloud.module.contract.controller.app.contract.vo.ApplyReqVO;
import com.xiaoshuidi.cloud.module.contract.controller.app.contract.vo.ApplyResp;
import com.xiaoshuidi.cloud.module.contract.convert.contract.ContractApplyConvert;
import com.xiaoshuidi.cloud.module.contract.dal.dataobject.bill.RentOrderDO;
import com.xiaoshuidi.cloud.module.contract.dal.dataobject.contract.ContractApplyDO;
import com.xiaoshuidi.cloud.module.contract.dal.dataobject.contract.ContractRoomDO;
import com.xiaoshuidi.cloud.module.contract.dal.mysql.contract.ContractAppleMapper;
import com.xiaoshuidi.cloud.module.contract.dal.mysql.contract.ContractRoomMapper;
import com.xiaoshuidi.cloud.module.contract.enums.ApiConstants;
import com.xiaoshuidi.cloud.module.contract.enums.ErrorCodeConstants;
import com.xiaoshuidi.cloud.module.contract.enums.bill.OrderTypeEnum;
import com.xiaoshuidi.cloud.module.contract.enums.contract.ContractApplyReningTypeEnum;
import com.xiaoshuidi.cloud.module.contract.enums.contract.ContractApplyStatus;
import com.xiaoshuidi.cloud.module.contract.enums.contract.ContractApplyType;
import com.xiaoshuidi.cloud.module.contract.enums.contract.ContractStatusEnum;
import com.xiaoshuidi.cloud.module.contract.mq.producer.ContractApplyProducer;
import com.xiaoshuidi.cloud.module.contract.service.bill.RentOrderService;
import com.xiaoshuidi.cloud.module.contract.service.contract.ContractApplyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ContractApplyServiceImpl extends ServiceImpl<ContractAppleMapper, ContractApplyDO> implements ContractApplyService {
    @Resource
    private ContractRoomMapper contractRoomMapper;
    @Resource
    private RentOrderService rentOrderService;
    @Resource
    private ContractApplyProducer contractApplyProducer;


    @Override
    @Transactional
    public ApplyResp apply(ApplyReqVO reqVO) {
        //查询合同信息
        Long contractId = reqVO.getContractId();
        ContractRoomDO contract = contractRoomMapper.selectById(contractId);
        if (contract == null) {
            throw new ServiceException(ErrorCodeConstants.CONTRACT_NOT_EXISTS);
        }
        if (!contract.getContractStatus().equals(ContractStatusEnum.ACTIVE.getCode()) &&
                !contract.getContractStatus().equals(ContractStatusEnum.CHECK_IN.getCode())) {
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_APPLY);
        }
        if (reqVO.getId() == null) {
            //同一个合同不能存在多个有效申请
            LambdaQueryWrapper<ContractApplyDO> wrapper = Wrappers.lambdaQuery();
            wrapper.eq(ContractApplyDO::getContractId, contractId)
                    .in(ContractApplyDO::getApplyStatus, CollectionUtil.newArrayList(ContractApplyStatus.IN_REVIEW.getCode(), ContractApplyStatus.TO_BE_EFFECTIVE.getCode()));
            long count = this.count(wrapper);
            if (count > 0) {
                throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_EXIST);
            }
        }
        log.info("合同信息：{}", JsonUtils.toJsonString(contract));
        //处理申请逻辑
        ContractApplyDO applyDO = this.applySave(reqVO, contract);
        ApplyResp resp = ContractApplyConvert.INSTANCE.convert2ApplyResp(applyDO);
        long becomeDue = ChronoUnit.DAYS.between(LocalDate.now(), contract.getEndTime());
        resp.setBecomeDue((int) becomeDue);
        resp.setApplyTypeStr(ContractApplyType.getMsgByCode(applyDO.getApplyType()));
        resp.setApplyStatusStr(ContractApplyStatus.getMsgByCode(applyDO.getApplyStatus()));
        return resp;
    }

    @Override
    @Transactional
    public ApplyResp appUpdate(ApplyReqVO reqVO) {
        if (!ContractApplyType.RENTING_OUT.getCode().equals(reqVO.getApplyType()) && !ContractApplyType.RENEWAL.getCode().equals(reqVO.getApplyType())) {
            log.error("非退租申请|续约申请不可修改");
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_UPDATE);
        }
        Long id = reqVO.getId();
        ContractApplyDO applyDO = this.getById(id);
        if (applyDO == null) {
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_EXIST);
        }
        if (!ContractApplyStatus.IN_REVIEW.getCode().equals(applyDO.getApplyStatus())) {
            log.error("非退租申请|续约申请不可修改");
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_UPDATE);
        }
        return this.apply(reqVO);
    }

    private ContractApplyDO applySave(ApplyReqVO reqVO, ContractRoomDO contract) {
        Long contractId = contract.getId();

        String applyType = reqVO.getApplyType();
        ContractApplyDO applyDo = new ContractApplyDO();
        applyDo.setId(reqVO.getId());
        applyDo.setApplyType(reqVO.getApplyType());
        applyDo.setContractId(contractId);
        applyDo.setApartmentId(contract.getApartmentId());
        applyDo.setApartmentName(contract.getApartmentName());
        applyDo.setRoomId(contract.getRoomId());
        applyDo.setRoomName(contract.getRoomName())
                .setSourceStartTime(contract.getStartTime())
                .setSourceEndTime(contract.getEndTime())
                .setEndTime(reqVO.getEndTime())
                .setCustomerName(contract.getCustomerName())
                .setCustomerPhone(contract.getCustomerPhone())
                .setMemberId(contract.getMemberId())
                .setCycle(reqVO.getCycle());
        if (reqVO.getId() == null) {
            applyDo.setApplyTime(LocalDateTime.now());
        }

        if (ContractApplyType.RENEWAL.getCode().equals(applyType)) {
            //申请续租逻辑
            applyDo.setStartTime(reqVO.getStartTime());
            applyDo.setMonthRent(contract.getMonthRent());
            applyDo.setProperty(contract.getProperty());
            applyDo.setApplyStatus(ContractApplyStatus.IN_REVIEW.getCode());
            //计算总费用 = 月租金 * 期数 + 月物业费 * 期数
            BigDecimal allTotal = BigDecimal.ZERO;
            if (reqVO.getCycle() != null && !reqVO.getCycle().equals(0)) {
                //租金
                BigDecimal totalRent = allTotal.add(contract.getMonthRent().multiply(BigDecimal.valueOf(reqVO.getCycle())));
                allTotal = allTotal.add(totalRent);
                applyDo.setTotalRent(totalRent);
                //物业费
                if (contract.getProperty() != null) {
                    BigDecimal totalProperty = contract.getProperty().multiply(BigDecimal.valueOf(reqVO.getCycle()));
                    allTotal = allTotal.add(totalProperty);
                    applyDo.setTotalProperty(totalProperty);
                }
            }
            applyDo.setAllTotal(allTotal);
            log.info("申请续租：月租金：{}, 物业费：{}, 总费用：{}", contract.getMonthRent(), contract.getProperty(), allTotal);
        } else if (ContractApplyType.EXTENSION.getCode().equals(applyType)) {
            applyDo.setApplyStatus(ContractApplyStatus.TO_BE_EFFECTIVE.getCode());
            //申请延期逻辑 日租金计算 = 月租金/合同到期第二天当月天数
            BigDecimal monthRent = contract.getMonthRent();
            int currentMonthDays = contract.getEndTime().plusDays(1).lengthOfMonth();
            //计算总费用 = 日租金 * 天数 * 3
            long days = ChronoUnit.DAYS.between(contract.getEndTime(), reqVO.getEndTime());
            BigDecimal daysRent = monthRent.divide(BigDecimal.valueOf(currentMonthDays), 2, RoundingMode.HALF_UP);
            BigDecimal allTotal = daysRent.multiply(BigDecimal.valueOf(days)).multiply(BigDecimal.valueOf(3)).setScale(2, BigDecimal.ROUND_UP);
            applyDo.setAllTotal(allTotal);
            applyDo.setMonthRent(daysRent);
            applyDo.setTotalRent(allTotal);
            applyDo.setStartTime(contract.getEndTime().plusDays(1));
            log.info("申请延期：月租金：{}, 当月天数：{}, 总费用：{}", monthRent, currentMonthDays, allTotal);
            //创建账单
            RentOrderDO rentOrderDO = createBill(applyDo);
            applyDo.setRentOrderId(rentOrderDO.getId());
            applyDo.setRentOrderNo(rentOrderDO.getOrderNo());
        } else {
            //申请退租逻辑
            applyDo.setApplyStatus(ContractApplyStatus.IN_REVIEW.getCode());
            //计算是到期退，还是提前退
            if (reqVO.getEndTime().isAfter(contract.getEndTime())) {
                applyDo.setRentingType(ContractApplyReningTypeEnum.EXPIRATION.getCode());
            } else {
                applyDo.setRentingType(ContractApplyReningTypeEnum.ADVANCE.getCode());
            }
        }
        this.saveOrUpdate(applyDo);
        if (ContractApplyType.EXTENSION.getCode().equals(applyType)) {
            //延期申请到期未支付 - 延迟取消
            contractApplyProducer.extensionCancel(applyDo.getId());
        }
        return applyDo;
    }

    /**
     * 延期申请单 - 创建账单
     *
     * @param applyDo
     */
    private RentOrderDO createBill(ContractApplyDO applyDo) {
        RentOrderCreateReq req = new RentOrderCreateReq();
        OrderFeesVo feesVo = new OrderFeesVo();
        feesVo.setFeeSort(ApiConstants.ONCE)
                .setFeeId(ApiConstants.RENT_FEE_ID)
                .setFeeName(ApiConstants.RENT_FEE_NAME)
                .setFeeAmount(applyDo.getAllTotal())
                .setPaymentCycle(1)
                .setHold(applyDo.getCustomerName());
        List<OrderFeesVo> feesVoList = new ArrayList<>();
        feesVoList.add(feesVo);
        req.setContractId(applyDo.getContractId())
                .setOrderType(OrderTypeEnum.EXTENSION.getCode())
                .setApartmentId(applyDo.getApartmentId())
                .setApartmentName(applyDo.getApartmentName())
                .setRoomId(applyDo.getRoomId())
                .setRoomName(applyDo.getRoomName())
                .setStartTime(applyDo.getStartTime())
                .setEndTime(applyDo.getEndTime())
                .setOughtPayTime(LocalDate.now())
                .setOrderFeesVo(feesVoList);
        log.info("申请单创建账单：{}", JsonUtils.toJsonString(req));
        List<RentOrderDO> rentOrder = rentOrderService.createRentOrder(req);
        log.info("创建账单成功");
        return rentOrder.get(0);
    }

    @Override
    public Boolean appRevoke(Long id) {
        ContractApplyDO applyDO = this.getById(id);
        if (applyDO == null) {
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_EXIST);
        }
        if (!ContractApplyStatus.IN_REVIEW.getCode().equals(applyDO.getApplyStatus())) {
            log.error("非退租申请|续约申请不可取消");
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_REVOKE);
        }
        applyDO.setApplyStatus(ContractApplyStatus.CANCELED.getCode());
        boolean b = this.updateById(applyDO);
        return b;
    }

    @Override
    public ApplyResp get(Long id) {
        ContractApplyDO applyDO = this.getById(id);
        ApplyResp resp = ContractApplyConvert.INSTANCE.convert2ApplyResp(applyDO);
        long becomeDue = ChronoUnit.DAYS.between(LocalDate.now(), applyDO.getSourceEndTime());
        resp.setBecomeDue((int) becomeDue);
        resp.setApplyTypeStr(ContractApplyType.getMsgByCode(applyDO.getApplyType()));
        resp.setApplyStatusStr(ContractApplyStatus.getMsgByCode(applyDO.getApplyStatus()));
        return resp;
    }

    @Override
    public PageResult<ApplyResp> appPage(PageParam req) {
        Page<ContractApplyDO> page = Page.of(req.getPageNo(), req.getPageSize());
        LambdaQueryWrapper<ContractApplyDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ContractApplyDO::getMemberId, SecurityFrameworkUtils.getLoginUserId())
                .orderByDesc(BaseDO::getCreateTime);
        this.page(page, wrapper);
        List<ContractApplyDO> records = page.getRecords();
        if (CollectionUtil.isNotEmpty(records)) {
            List<ApplyResp> resps = records.stream().map(s -> {
                ApplyResp resp = ContractApplyConvert.INSTANCE.convert2ApplyResp(s);
                long becomeDue = ChronoUnit.DAYS.between(LocalDate.now(), s.getSourceEndTime());
                resp.setBecomeDue((int) becomeDue);
                resp.setApplyTypeStr(ContractApplyType.getMsgByCode(s.getApplyType()));
                resp.setApplyStatusStr(ContractApplyStatus.getMsgByCode(s.getApplyStatus()));
                return resp;
            }).collect(Collectors.toList());
            return new PageResult<>(resps, page.getTotal());
        }
        return new PageResult<>(page.getTotal());
    }

    @Override
    public PageResult<ContractApplyPageResp> page(ContractApplyPageReq req) {
        Page<ContractApplyDO> page = Page.of(req.getPageNo(), req.getPageSize());
        LambdaQueryWrapper<ContractApplyDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ContractApplyDO::getApplyType, req.getApplyType())
                .ge(StrUtil.isNotEmpty(req.getStartTime()), ContractApplyDO::getApplyTime, req.getStartTime())
                .eq(StrUtil.isNotEmpty(req.getApplyStatus()), ContractApplyDO::getApplyStatus, req.getApplyStatus())
                .and(StrUtil.isNotEmpty(req.getSearch()), s -> {
                    s.like(ContractApplyDO::getCustomerName, req.getSearch())
                            .or().like(ContractApplyDO::getCustomerPhone, req.getSearch());
                });
        if (StrUtil.isNotEmpty(req.getEndTime())) {
            wrapper.le(ContractApplyDO::getApplyTime, LocalDateTimeUtil.parseDate(req.getEndTime(), "yyyy-MM-dd").plusDays(1));
        }
        wrapper.orderByDesc(ContractApplyDO::getApplyTime);
        this.page(page, wrapper);
        List<ContractApplyDO> records = page.getRecords();
        if (CollectionUtil.isNotEmpty(records)) {
            List<ContractApplyPageResp> resps = records.stream().map(s -> {
                ContractApplyPageResp resp = ContractApplyConvert.INSTANCE.convert(s);
                resp.setApplyStatusStr(ContractApplyStatus.getMsgByCode(s.getApplyStatus()));
                resp.setRentingTypeStr(ContractApplyReningTypeEnum.getMsgByCode(s.getRentingType()));
                return resp;
            }).collect(Collectors.toList());
            return new PageResult<>(resps, page.getTotal());
        }
        return new PageResult<>(page.getTotal());
    }

    @Override
    public Boolean approve(Long id, boolean flag) {
        ContractApplyDO ContractApplyDO = this.getById(id);
        if (ContractApplyDO == null) {
            throw new ServiceException(ErrorCodeConstants.CONTRACT_APPLY_NOT_EXIST);
        }
        ContractApplyDO.setAuditTime(LocalDateTime.now());
        ContractApplyDO.setReviewedBy(SecurityFrameworkUtils.getLoginUser().getNickname());
        ContractApplyDO.setReviewedPhone(SecurityFrameworkUtils.getLoginUser().getMobile());
        if (flag) {
            ContractApplyDO.setApplyStatus(ContractApplyStatus.EFFECTIVE.getCode());
        } else {
            ContractApplyDO.setApplyStatus(ContractApplyStatus.REJECTED.getCode());
        }
        boolean b = this.updateById(ContractApplyDO);
        return b;
    }

    @Override
    public boolean paymentCompleted(Long orderId) {
        LambdaQueryWrapper<ContractApplyDO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ContractApplyDO::getRentOrderId, orderId);
        ContractApplyDO applyDO = this.getOne(wrapper);
        if (applyDO == null) {
            log.info("该申请单不存在");
            return true;
        }
        applyDO.setApplyStatus(ContractApplyStatus.EFFECTIVE.getCode());
        return this.updateById(applyDO);
    }

    /**
     * 统计未完成的租客申请数量
     * @return
     */
    @Override
    public CommonResult<Long> contractApplyNum() {
        LambdaQueryWrapper<ContractApplyDO> wrapper = Wrappers.lambdaQuery();
        wrapper.in(ContractApplyDO::getApplyStatus,
                CollectionUtil.toList(
                ContractApplyStatus.IN_REVIEW.getCode(),
                ContractApplyStatus.TO_BE_EFFECTIVE.getCode())
        );
        return CommonResult.success(this.count(wrapper));
    }
}
